在開始今天的主題前,雖然在前面介紹 Slate 時已經有稍微提到過了,我們還是先從 slate 的各個 packages 分別負責的領域開始介紹起。
首先第一步先從確定工作量開始,我們直接來看 slate package 的 src 之下的 directory 吧!
咦?這個量怎麼跟我預期的不太一樣,它不是負責處理整個核心邏輯的嗎?應該要多一點才對吧?
這就是 Schema-less 這項特性的美妙之處了,我們在 Day8 的時候也有提到過 Slate 本質上是提供給開發者一個 full-customizable 的 DOM like editor , 它不會猜測各種複雜的使用情境,只提供給開發者最基本的 concepts 以及 method-apis,也因此它的工作量比一般的 libraries 要少上許多。
我們先為上圖的第一層 folders 與 file 區分他們所負責的功能:
interfaces/ :定義了所有 slate 使用到的概念的 Base type(同時也包含 BaseEditor
的 interface 以及 low-level action 的 Operation types),以及這些概念所提供的 method apis 。
transforms/ : Transforms 、 Operations 為在新版 Slate 的世界中,二個主要操作 immutable editor 的方法,讓開發者可以透過它們來自由操作 editor state 。
其實 Slate 裡還有一個詞叫 "Command" ,在新、舊版的 Slate 中都有出現,卻代表不同的意義,筆者一開始也被這幾個詞搞得暈頭轉向。
舊版 Slate 的 Command 其實就是新版的 Transforms ,他們的用途一樣是提供給開發者操作 editor 的 methods ,只是新版另外整理成一套更完整的介面給開發者使用,他們本質上是一樣的。
新版 Command 的重點則是 "Custom" 這一詞,是提供給開發者搭配 Transforms 與 Operations 去製作貼近於自己創造的編輯器 use-case 的 reusable function ,官方文件上是將它視為 high-level actions ,但其實看起來就是提供一個名義讓開發者自由搭配 Transforms 與 Operations 去建立自己的 helper function ,可能留個名字給它看起來比較炫砲吧 XD
附上 官方文件 上提供的寫法,讀者可以先加減看一下,我們在之後介紹到 interface/editor.ts 的文章時會再提到它。
import { Editor } from 'slate';
const MyEditor = {
...Editor,
insertParagraph(editor) {
// ...
},
}
utils/ :一些輔助 slate package 內部使用的函數
create-editor.ts :提供負責 build 並 return immutable editor 的 create function ,而 function 裡做的事就是初始化 editor 裡頭的資料,包含 data model ,以及各種 behaviors 與 actions 。
來偷瞄一眼 interfaces/editor.ts
裡 BaseEditor 的 interface 是怎麼定義的
export interface BaseEditor {
children: Descendant[]
selection: Selection
operations: Operation[]
marks: Omit<Text, 'text'> | null
// Schema-specific node behaviors.
isInline: (element: Element) => boolean
isVoid: (element: Element) => boolean
normalizeNode: (entry: NodeEntry) => void
onChange: () => void
// Overrideable core actions.
addMark: (key: string, value: any) => void
apply: (operation: Operation) => void
deleteBackward: (unit: 'character' | 'word' | 'line' | 'block') => void
deleteForward: (unit: 'character' | 'word' | 'line' | 'block') => void
deleteFragment: (direction?: 'forward' | 'backward') => void
getFragment: () => Descendant[]
insertBreak: () => void
insertFragment: (fragment: Node[]) => void
insertNode: (node: Node) => void
insertText: (text: string) => void
removeMark: (key: string) => void
}
這就是 slate 回傳給開發者的 editor state 的 interface 。
children
、 selection
、 marks
、 operations
儲存了整個 slate editor 需要的資料,我們會在 Data-Model 的篇章 深入介紹。
再來有個值得注意的是 onChange
這個 function , 在執行 Transform methods 時, Slate 其實會在內部觸發多次的 Operations 分次去修改 Editor value ,如果我們只純粹以 State change 作為畫面 re-render 的判斷依據的話就會導致『一次 Transform 就伴隨著多次不必要的 re-render』,這當然不是我們所樂見的, Slate 也知道我們不喜歡,於是它利用了 Promise Micro-Task 製作了 FLUSHING 機制,提供 onChange
這個 overrideable 的 function 讓開發者能在正確的時機點 re-render 畫面。
其餘的則為官方提供的 build-in methods ,開發者可以搭配使用這些 Overrideable actions 去建立 Command
。
接著我們上個簡單的 whimsical 圖來看看 Slate 的基本運作概念:
createEditor
函式取得 editor ,並主要透過 Transform methods 操作 editor state。children
與 selection
會再經過一層 Immer 的包裝做轉換。onChange
function 。如果我們試著省略中間較為繁瑣的過程,將它們概括為一個週期產生一組的 Actions (行為)的話,不難發現它其實就是一組經典的單向資料流的模型。
這就是最基本、簡化過後的 slate 運作模型,先讓讀者有個畫面,接下來我們會將目光聚焦於 interfaces/ ,那我們就一樣下一篇見啦!